﻿//This work is free software; you can redistribute it and/or modify it under the terms of the 
//GNU General Public License as published by the Free Software Foundation; either version 3 of 
//the License, or any later version. This work is distributed in the hope that it will be useful, 
//but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
//A PARTICULAR PURPOSE. See version 3 of the GNU General Public License for more details.

//Originally Created by AdmiralJonB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Generic_Genetic_Algorithm_Class
{
    public class GenericGeneticAlgorithm
    {
        #region Variables

        public struct PopulationMember
        {
            /// <summary>
            /// Represents the Evaluation of this particular function
            /// </summary>
            private float _Evaluation;
            public float Evaluation
            {
                get { return _Evaluation; }
                set
                {
                    _Evaluation = value;
                }
            }

            private float[] _FloatGene;
            public float[] FloatGene
            {
                get { return _FloatGene; }
                set
                {
                    _FloatGene = value;
                }
            }

            private string[] _StringGene;
            public string[] StringGene
            {
                get { return _StringGene; }
                set
                {
                    _StringGene = value;
                }
            }
        }

        private float _MutationRate;
        public float MutationRate
        {
            get { return _MutationRate; }
            set { _MutationRate = value; }
        }

        private PopulationMember[] _Population;
        public PopulationMember[] Population
        {
            get{ return _Population; }
        }

        private PopulationMember[] _OmittedMembers;
        public PopulationMember[] OmittedMembers
        {
            get { return _OmittedMembers; }
        }

        private float[] GeneLimits;
        private String[] GenePossibilities;

        private int _TypeOfGene;
        public int TypeOfGene
        {
            get { return _TypeOfGene; }
        }

        private int _BreedingSelection;
        public int BreedingSelection
        {
            get { return _BreedingSelection; }
            set
            {
                _BreedingSelection = value;
            }
        }

        private int _BreedingType;
        public int BreedingType
        {
            get { return _BreedingType; }
            set
            {
                _BreedingType = value;
            }
        }

        private int _MultiPointCrossover;
        public int MultiPointCrossover
        {
            get { return _MultiPointCrossover; }
            set
            {
                _MultiPointCrossover = value;
            }
        }

        private int _MutationType;
        public int MutationType
        {
            get { return _MutationType; }
            set
            {
                _MutationType = value;
            }
        }

        private int _NextGenerationSelection;
        public int NextGenerationSelection
        {
            get { return _NextGenerationSelection; }
            set
            {
                _NextGenerationSelection = value;
            }
        }

        private int _GeneSize;
        public int GeneSize
        {
            get { return _GeneSize; }
        }

        private Random RanGen;
        
        #endregion

        #region Initialisation Routines
        /// <summary>
        /// Sets the Initial Values
        /// </summary>
        private void SetInitialValues(int GeneType, int BreedSel, int BreedType, int noCrossover, int MutationType, int NextGenSel, int GeneSize)
        {
            _TypeOfGene = GeneType;
            _BreedingSelection = BreedSel;
            _BreedingType = BreedType;
            _MultiPointCrossover = noCrossover;
            _MutationType = MutationType;
            _NextGenerationSelection = NextGenSel;
            _GeneSize = GeneSize;

            RanGen = new Random();
        }

        /// <summary>
        /// Instantiates the Class
        /// </summary>
        /// <param name="Mutation">The Initial Mutation Rate</param>
        /// <param name="PopulationSize">The Size of the Population</param>
        /// <param name="Limits">A list of all the possible chromosomes in a gene</param>
        /// <param name="GeneSize">The Size of each Gene</param>
        public GenericGeneticAlgorithm(float Mutation, int PopulationSize, int GeneSize, String[] Limits)
        {
            _MutationRate = Mutation;
            _Population = new PopulationMember[PopulationSize];
            GenePossibilities = Limits;
            _OmittedMembers = null;

            SetInitialValues(1, 0, 0, 1, 0, 0, GeneSize);

            //Initialise every member in the population
            for (int i = 0; i < _Population.Length; i++)
            {
                _Population[i] = new PopulationMember();
                _Population[i].Evaluation = 0;
                _Population[i].StringGene = new String[GeneSize];
            }

            GenerateRandomPopulation();
        }

        /// <summary>
        /// Instantiates the Class
        /// </summary>
        /// <param name="Mutation">The Initial Mutation Rate</param>
        /// <param name="PopulationSize">The Size of the Population</param>
        /// <param name="Limits">A 2 Dimensional Array of the minimum and maximum a chromosome can be</param>
        /// <param name="GeneSize">The Size of each Gene</param>
        public GenericGeneticAlgorithm(float Mutation, int PopulationSize, int GeneSize, float[] Limits)
        {
            _MutationRate = Mutation;
            _Population = new PopulationMember[PopulationSize];
            _OmittedMembers = null;
            GeneLimits = Limits;

            SetInitialValues(0, 0, 0, 1, 0, 0, GeneSize);

            //Initialise every member in the population
            for (int i = 0; i < _Population.Length; i++)
            {
                _Population[i] = new PopulationMember();
                _Population[i].Evaluation = 0;
                _Population[i].FloatGene = new float[GeneSize];
            }

            GenerateRandomPopulation();
        }
        #endregion

        public void GenerateRandomPopulation()
        {
            //If the Gene is of type 'float'
            if (TypeOfGene == 0)
            {
                float[] Limits = { GeneLimits[0], GeneLimits[1] };
                int Scale = 1;

                //Scale the numbers for the Random Number Generator
                while (Math.Round(Limits[0],0) != Limits[0] | Math.Round(Limits[1],0) != Limits[1])
                {
                    Limits[0] *= 10;
                    Limits[1] *= 10;
                    Scale *= 10;
                }

                //Generate Random Population Members within the limits
                for (int i = 0; i < Population.Length; i++)
                {
                    for (int j = 0; j < Population[i].FloatGene.Length; j++)
                    {
                        Population[i].FloatGene[j] = RanGen.Next(Convert.ToInt32(Limits[0]), Convert.ToInt32(Limits[1]) + 1);
                        Population[i].FloatGene[j] /= Scale;
                    }
                }
            }

            //If the Gene is of type 'string'
            else if (TypeOfGene == 1)
            {
                int StringTypeSize = GenePossibilities.Length;

                //Generate Random Population Members within the limits
                for (int i = 0; i < Population.Length; i++)
                {
                    for (int j = 0; j < Population[i].StringGene.Length; j++)
                    {
                        Population[i].StringGene[j] = GenePossibilities[RanGen.Next(StringTypeSize)];
                    }
                }
            }
        }

        public void NewGeneration(float[] Evaluations)
        {

            //Selection for Breeding
            // - Roullette Selection
            // - Random Selection
            //Breeding
            // - Multi-Point Crossover
            //Mutation (Modular/Singular)
            // - Random
            // - Standard Distribution Probability
            //Selection for Next Generation
            // - Roullette Selection
            // - Random Selection
            // - Static Selection
            
            if (Evaluations.Length != Population.Length)
                throw new System.Exception("There is a different amount of evaluation functions to amount of members in the Population.");

            for (int i = 0; i < Population.Length; i++)
            {
                Population[i].Evaluation = Evaluations[i];
            }

            OrderPopulationMax();
            int[] BreedSelection = RoulletteSelection();


        }

        /// <summary>
        /// Orders the Population by the evaluation functions, maximum first.
        /// </summary>
        private void OrderPopulationMax()
        {
            bool Sorted = false;

            //Bubble Sort on the Population, Maximum at position 0
            while(!Sorted)
            {
                Sorted = true;

                for (int i = 0; i < Population.Length - 1; i++)
                {
                    if (Population[i].Evaluation < Population[i + 1].Evaluation)
                    {
                        PopulationMember TempMember = Population[i + 1];
                        Population[i + 1] = Population[i];
                        Population[i] = TempMember;
                        Sorted = false;
                    }
                }
            }
        }

        /// <summary>
        /// Carries out a Roullette Selection to select approximately 1/4 of the population
        /// </summary>
        /// <returns>Returns Array Positions of Members Selected</returns>
        private int[] RoulletteSelection()
        {
            int popby4, scale;
            int[] positions;
            float max = 0;

            //Calculates the maximum value when summated
            for (int i = 0; i < Population.Length; i++)
            {
                max += Population[i].Evaluation;
            }

            scale = 1;

            //Scale max so can be used for random number selection
            while (Math.Round(max,0) != max)
            {
                max *= 10;
                scale *= 10;
            }

            //Preparation for Selection
            popby4 = Population.Length / 4;
            positions = new int[popby4];

            
            for (int i = 0; i < popby4; i++)
            {
                float value = ((float)RanGen.Next(0, (int)max)) / scale;
                float count = 0;

                //Roullete Selection Loop
                for (int j = 0; j < Population.Length; j++)
                {
                    count += Population[j].Evaluation;
                    if (count > value)
                    {
                        positions[i] = j;
                        break;
                    }
                }

                //Makes sure there are no duplicate positions selected
                if (i != 0)
                    for (int j = i - 1; j > 0; j--)
                    {
                        if (positions[i] == positions[j])
                            i--;
                    }
            }

            return positions;
        }

        

    }
}
